• Jump To … +
    main.js separate.js single.js web-apg-api.js main.js web-conv-api.js ast.js csv.js dangling-else.js display.js flags.js float.js limits.js main.js multiline-mode.js recursive.js replace.js rules.js split.js testonly.js trace.js udt.js unicode.js web-email.js word-boundaries.js main.js phone-number.js web-main.js web-phone-number.js main.js phone-number.js setup.js translate.js xml.js branch-fail-grammar.js main.js parent-mode-grammar.js setup.js universal-mode-grammar.js colors-app.js colors-callbacks.js colors.js main.js more-app.js more-setup.js more.js ast-callbacks.js bad-input.js basic.js ini-file.js main.js parser-callbacks.js setup.js trace.js anbncn.js and.js c-comment.js compound.js main.js nested.js not.js setup.js boundaries-grammar.js boundaries.js comment-grammar.js comment.js main.js negative-grammar.js negative.js positive-grammar.js positive.js setup.js main.js odata-grammar.js run.js setup.js area-code.js lookaround.js main.js phone-number.js setup.js simple.js all-operators.js default.js fancy-number.js limited-lines.js main.js select-operators.js select-rules.js setup.js main.js minimal.js parent-u.js parent.js phone-number.js setup.js stats.js trace.js universal-u.js universal.js callbacks.js grammar.js main.js parser.js writeHtml.js LICENSE.md README.md index.md
  • setup.js

  • §
    /* eslint-disable new-cap */
    /* eslint-disable guard-for-in */
    /* eslint-disable no-restricted-syntax */
    /*  *************************************************************************************
     *   copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
     *     license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
     *   ********************************************************************************* */
  • §

    This module provides an example of how a more “real world” parser might be built. It parses the INI file format, commonly used as a configuration file for many types of applications. The grammar (ini-file.bnf) presented here includes “error productions”. That is, rules are defined to catch errors in the configuration file format. This way, instead of simply having the parser fail on bad input (a very frustrating user experience) the application can provide callback functions to catch the errors and report them to the user.

    To keep things modular the syntax and AST callback functions are defined in separate modules, parser-callbacks.js and ast-callbacks.js, resectively, and “require()”ed by the application as needed.

    • input - the input string to parse
    • trace - if true display the trace
    • stats - if true display the stats
    module.exports = function setup(input, traceArg, statsArg) {
      let trace = traceArg;
      let stats = statsArg;
      const thisFileName = 'setup.js: ';
      const nodeUtil = require('node:util');
      const writeHtml = require('../writeHtml');
      const inspectOptions = {
        showHidden: true,
        depth: null,
      };
      /* build an array of names (strings) and sort them alphabetically */
      const sortNames = function sortNames(obj) {
        const names = [];
        for (const name in obj) {
          names.push(name);
        }
        return names.sort();
      };
      try {
        if (typeof input !== 'string') {
          throw new Error(`${thisFileName}invalid input string`);
        }
        if (trace === null || typeof trace !== 'object' || trace.traceObject !== 'traceObject') {
          trace = null;
        }
        if (stats === null || typeof stats !== 'object' || stats.statsObject !== 'statsObject') {
          stats = null;
        }
    
        /* Get the required parser components (see [`simple/setup.js`](../simple/setup.html) for the basics of setting up the parser */
        const { apgLib } = require('apg-js');
        const parser = new apgLib.parser();
        const grammar = new (require('./ini-file'))();
        const parserCallbacks = new (require('./parser-callbacks'))();
        const astCallbacks = new (require('./ast-callbacks'))();
        parser.ast = new apgLib.ast();
    
        /* Define the syntax callback functions to the parser. */
        parser.callbacks = parserCallbacks.callbacks;
    
        /* Define the semantic (`AST`) callback functions to the parser. */
        parser.ast.callbacks = astCallbacks.callbacks;
        parser.trace = trace;
        parser.stats = stats;
        const inputCharacterCodes = apgLib.utils.stringToChars(input);
    
        /* object to hold error messages from the syntax callback functions */
        const syntaxData = {};
    
        /*
            Parse the input string. The syntax callback functions simply
            monitor the parser, reporting any syntax errors it finds in the input string.
            */
        const result = parser.parse(grammar, 0, inputCharacterCodes, syntaxData);
        console.log();
        console.log("the parser's results");
        console.dir(result, inspectOptions);
        if (result.success !== true) {
          throw new Error(`${thisFileName}parse failed`);
        }
        if (syntaxData.errors.length > 0) {
          console.log();
          console.log('syntax errors found:');
          syntaxData.errors.forEach((error) => {
            console.log(error);
          });
        }
        let html;
        if (parser.stats !== null) {
          html = parser.stats.toHtmlPage('hits', 'rules ordered by hit count', 'IniFile Stats');
          writeHtml(html, 'ini-file-stats');
        }
        if (parser.trace !== null) {
          /* Display the trace, if requested */
          html = parser.trace.toHtmlPage('ascii', 'IniFile Trace', 'IniFile Trace');
          writeHtml(html, 'ini-file-trace');
        }
    
        /* object to hold the parsed data */
        const data = {};
    
        /*
            The syntax is correct, so translate the ini file format data into a JSON-like object.
            Alphabetize the section names and the key names within each section and display on the console.
            */
        parser.ast.translate(data);
        console.log();
        console.log('alphabetized AST translation data:');
        console.log();
        const sectionNames = sortNames(data);
        sectionNames.forEach((sectionName) => {
          if (sectionName !== '0') {
            console.log();
            console.log(`[${sectionName}]`);
          }
          const keys = sortNames(data[sectionName]);
          keys.forEach((keyName) => {
            console.log(`${keyName}: ${data[sectionName][keyName]}`);
          });
        });
      } catch (e) {
        let msg = '\nEXCEPTION THROWN: \n';
        if (e instanceof Error) {
          msg += `${e.name}: ${e.message}`;
        } else if (typeof e === 'string') {
          msg += e;
        } else {
          msg += nodeUtil.inspect(e, inspectOptions);
        }
        process.exitCode = 1;
        console.log(msg);
        throw e;
      }
    };